﻿using System.Collections.Generic;
using System.ComponentModel;
using System.Windows;
using SS.WPF.Framework.Localization;
using SS.WPF.Toolkit.PropertyEditor.Comparer;

namespace SS.WPF.Toolkit.PropertyEditor
{
    using System;

    /// <summary>
    /// The class is used to define a property for item
    /// <para>
    /// This class implements interface IWeakEventListener to listent the event that is notified when Languaged changed
    /// </para>
    /// </summary>
    public sealed class Property : Item, IWeakEventListener
    {
        #region Fields

        public static readonly IComparer<Property> CompareByCategoryThenByName = new ByCategoryThenByNameComparer();
        public static readonly IComparer<Property> CompareByName = new ByNameComparer();

        /// <summary>
        /// Refer to Property source
        /// </summary>
        private readonly object _propertySource;

        /// <summary>
        /// Refer to an instance of implementation of PropertyDescriptor
        /// </summary>
        private readonly PropertyDescriptor _property;
        
        #endregion

        #region Properties

        /// <summary>
        /// This property is used in case user want to assign Item to any Group
        /// <para>
        /// When this property is set, the properties in Group will be notified
        /// </para>
        /// </summary>
        private string _validationGroup;
        public string ValidationGroup
        {
            get
            {
                return this._validationGroup;
            }
            set
            {
                this._validationGroup = value;
                this.NotifyOfPropertyChange(() => this.ValidationGroup);
            }
        }

        /// <summary>
        /// Refer to category of Property
        /// </summary>
        public string Category
        {
            get
            {
                return this._property.Category;
            }
        }

        /// <summary>
        /// Refer to description of Property
        /// </summary>
        public string Description
        {
            get
            {
                return this._property.Description;
            }
        }

        /// <summary>
        /// Determine whether this Property is readonly
        /// </summary>
        public bool IsReadOnly
        {
            get
            {
                return this._property.IsReadOnly;
            }
        }

        /// <summary>
        /// Determine whether this Property is writeable
        /// </summary>
        public bool IsWriteable
        {
            get
            {
                return !this.IsReadOnly;
            }
        }

        /// <summary>
        /// Refer to Name of property
        /// </summary>
        public string Name
        {
            get
            {
                return this._property.DisplayName ?? this._property.Name;
            }
        }

        /// <summary>
        /// Refer to Type of property
        /// </summary>
        public Type PropertyType
        {
            get
            {
                return this._property.PropertyType;
            }
        }

        /// <summary>
        /// Get value of Property
        /// <para>
        /// The value is got from correspondent Property of PropertySource
        /// </para>
        /// </summary>
        public object Value
        {
            get
            {
                return this._property.GetValue(this._propertySource);
            }
            set
            {
                var currentValue = this._property.GetValue(this._propertySource);
                if (value != null && value.Equals(currentValue))
                {
                    return;
                }
                var propertyType = this._property.PropertyType;
                if (propertyType == typeof(object) || value == null && propertyType.IsClass ||
                    value != null && propertyType.IsInstanceOfType(value))
                {
                    this._property.SetValue(this._propertySource, value);
                }
                else
                {
                    var converter = TypeDescriptor.GetConverter(this._property.PropertyType);
                    try
                    {
                        var convertedValue = converter.ConvertFrom(value);
                        this._property.SetValue(this._propertySource, convertedValue);
                    }
                    catch (Exception)
                    {
                        return;
                    }
                }
                this.NotifyOfPropertyChange(() => this.Value);
            }
        }

        /// <summary>
        /// Keep orginal data
        /// <para>
        /// The original data is used when user want to reset any data changed to orginal
        /// </para>
        /// </summary>
        private object _originalData;
        public object OriginalData
        {
            get
            {
                return this._originalData;
            }
            set
            {
                this._originalData = value;
                this.NotifyOfPropertyChange(() => this.OriginalData);
            }
        }

        /// <summary>
        /// Determine whether original value of Property has been changed
        /// </summary>
        private bool _isDirty;
        public bool IsDirty
        {
            get
            {
                return this._isDirty;
            }
            set
            {
                this._isDirty = value;
                this.NotifyOfPropertyChange(() => this.IsDirty);
            }
        }

        #endregion

        #region Ctors

        /// <summary>
        /// Create instant of Property with sepecified inputs
        /// </summary>
        /// <param name="propertySource">Refer to instance PropertySource</param>
        /// <param name="property">Refer to instance PropertyDescriptor</param>
        public Property(object propertySource, PropertyDescriptor property)
        {
            if (propertySource is ICustomTypeDescriptor)
            {
                this._propertySource = ((ICustomTypeDescriptor)propertySource).GetPropertyOwner(property);
            }
            else
            {
                this._propertySource = propertySource;
            }

            this._property = property;

            this._property.AddValueChanged(this._propertySource, this.InstancePropertyChanged);

            this.NotifyOfPropertyChange(() => this.PropertyType);
        }

        #endregion

        #region Help methods

        private void InstancePropertyChanged(object sender, EventArgs e)
        {
            this.NotifyOfPropertyChange(() => this.Value);
        }

        public override string ToString()
        {
            return this.Value == null ? base.ToString() : this.Value.ToString();
        }

        #endregion

        #region Implementation of IWeakEventListener

        /// <summary>
        /// Receives events from the centralized event manager.
        /// </summary>
        /// <returns>
        /// true if the listener handled the event. It is considered an error by the <see cref="T:System.Windows.WeakEventManager"/> handling in WPF to register a listener for an event that the listener does not handle. Regardless, the method should return false if it receives an event that it does not recognize or handle.
        /// </returns>
        /// <param name="managerType">The type of the <see cref="T:System.Windows.WeakEventManager"/> calling this method.</param><param name="sender">Object that originated the event.</param><param name="e">Event data.</param>
        public bool ReceiveWeakEvent(Type managerType, object sender, EventArgs e)
        {
            if (managerType == typeof(LanguageChangedEventManager))
            {
                OnLanguageChanged(sender, e);
                return true;
            }
            return false;
        }

        /// <summary>
        /// This method is invoked when Language has been changed
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void OnLanguageChanged(object sender, EventArgs e)
        {
            this.NotifyOfPropertyChange(() => this.Name);
            if (this.Value.GetType().IsEnum)
            {
                this.NotifyOfPropertyChange(() => this.Value);
            }
        }

        #endregion
    }
}
    